package com.dodolive.app.lib.commonmark.internal;

import com.dodolive.app.lib.commonmark.internal.inline.AsteriskDelimiterProcessor;
import com.dodolive.app.lib.commonmark.internal.inline.UnderscoreDelimiterProcessor;
import com.dodolive.app.lib.commonmark.internal.util.Escaping;
import com.dodolive.app.lib.commonmark.internal.util.Html5Entities;
import com.dodolive.app.lib.commonmark.internal.util.LinkScanner;
import com.dodolive.app.lib.commonmark.internal.util.Parsing;
import com.dodolive.app.lib.commonmark.node.Code;
import com.dodolive.app.lib.commonmark.node.HardLineBreak;
import com.dodolive.app.lib.commonmark.node.HtmlInline;
import com.dodolive.app.lib.commonmark.node.Image;
import com.dodolive.app.lib.commonmark.node.Link;
import com.dodolive.app.lib.commonmark.node.LinkReferenceDefinition;
import com.dodolive.app.lib.commonmark.node.MentionLinkNode;
import com.dodolive.app.lib.commonmark.node.Node;
import com.dodolive.app.lib.commonmark.node.SoftLineBreak;
import com.dodolive.app.lib.commonmark.node.Text;
import com.dodolive.app.lib.commonmark.parser.InlineParser;
import com.dodolive.app.lib.commonmark.parser.InlineParserContext;
import com.dodolive.app.lib.commonmark.parser.delimiter.DelimiterProcessor;

import java.util.Arrays;
import java.util.BitSet;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

public class InlineParserImpl implements InlineParser {

  //菱形语法匹配
  private static final String MD_PATTERN_STR = "<[@|#](all|([!|&]?\\d+))>";
  private static final Pattern MD_PATTERN = Pattern.compile('^' + MD_PATTERN_STR, Pattern.CASE_INSENSITIVE);

  private static final String HTMLCOMMENT = "<!---->|<!--(?:-?[^>-])(?:-?[^-])*-->";
  private static final String PROCESSINGINSTRUCTION = "[<][?].*?[?][>]";
  private static final String DECLARATION = "<![A-Z]+\\s+[^>]*>";
  private static final String CDATA = "<!\\[CDATA\\[[\\s\\S]*?\\]\\]>";
  private static final String HTMLTAG = "(?:" + Parsing.OPENTAG + "|" + Parsing.CLOSETAG + "|" + HTMLCOMMENT
      + "|" + PROCESSINGINSTRUCTION + "|" + DECLARATION + "|" + CDATA + ")";

  private static final String ASCII_PUNCTUATION = "!\"#\\$%&'\\(\\)\\*\\+,\\-\\./:;<=>\\?@\\[\\\\\\]\\^_`\\{\\|\\}~";
  private static final Pattern PUNCTUATION = Pattern
      .compile("^[" + ASCII_PUNCTUATION + "\\p{Pc}\\p{Pd}\\p{Pe}\\p{Pf}\\p{Pi}\\p{Po}\\p{Ps}]");

  private static final Pattern HTML_TAG = Pattern.compile('^' + HTMLTAG, Pattern.CASE_INSENSITIVE);

  private static final Pattern ESCAPABLE = Pattern.compile('^' + Escaping.ESCAPABLE);

  private static final Pattern ENTITY_HERE = Pattern.compile('^' + Escaping.ENTITY, Pattern.CASE_INSENSITIVE);

  private static final Pattern TICKS = Pattern.compile("`+");

  private static final Pattern TICKS_HERE = Pattern.compile("^`+");

  private static final Pattern EMAIL_AUTOLINK = Pattern
      .compile("^<([a-zA-Z0-9.!#$%&'*+/=?^_`{|}~-]+@[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?(?:\\.[a-zA-Z0-9](?:[a-zA-Z0-9-]{0,61}[a-zA-Z0-9])?)*)>");

  private static final Pattern AUTOLINK = Pattern
      .compile("^<[a-zA-Z][a-zA-Z0-9.+-]{1,31}:[^<>\u0000-\u0020]*>");

  private static final Pattern SPNL = Pattern.compile("^ *(?:\n *)?");

  private static final Pattern UNICODE_WHITESPACE_CHAR = Pattern.compile("^[\\p{Zs}\t\r\n\f]");

  private static final Pattern WHITESPACE = Pattern.compile("\\s+");

  private static final Pattern FINAL_SPACE = Pattern.compile(" *$");

  private final BitSet specialCharacters;
  private final BitSet delimiterCharacters;
  private final Map<Character, DelimiterProcessor> delimiterProcessors;
  private final InlineParserContext context;

  private String input;
  private int index;

  /**
   * Top delimiter (emphasis, strong emphasis or custom emphasis). (Brackets are on a separate stack, different
   * from the algorithm described in the spec.)
   */
  private Delimiter lastDelimiter;

  /**
   * Top opening bracket (<code>[</code> or <code>![)</code>).
   */
  private Bracket lastBracket;

  public InlineParserImpl(InlineParserContext inlineParserContext) {
    this.delimiterProcessors = calculateDelimiterProcessors(inlineParserContext.getCustomDelimiterProcessors());
    this.delimiterCharacters = calculateDelimiterCharacters(this.delimiterProcessors.keySet());
    this.specialCharacters = calculateSpecialCharacters(delimiterCharacters);

    this.context = inlineParserContext;
  }

  public static BitSet calculateDelimiterCharacters(Set<Character> characters) {
    BitSet bitSet = new BitSet();
    for (Character character : characters) {
      bitSet.set(character);
    }
    return bitSet;
  }

  public static BitSet calculateSpecialCharacters(BitSet delimiterCharacters) {
    BitSet bitSet = new BitSet();
    bitSet.or(delimiterCharacters);
    bitSet.set('\n');
    bitSet.set('`');
    bitSet.set('[');
    bitSet.set(']');
    bitSet.set('\\');
    bitSet.set('!');
    bitSet.set('<');
    bitSet.set('&');
//        bitSet.set('|');
    return bitSet;
  }

  public static Map<Character, DelimiterProcessor> calculateDelimiterProcessors(List<DelimiterProcessor> delimiterProcessors) {
    Map<Character, DelimiterProcessor> map = new HashMap<>();
    addDelimiterProcessors(Arrays.<DelimiterProcessor>asList(new AsteriskDelimiterProcessor(), new UnderscoreDelimiterProcessor()), map);
    addDelimiterProcessors(delimiterProcessors, map);
    return map;
  }

  private static void addDelimiterProcessors(Iterable<DelimiterProcessor> delimiterProcessors, Map<Character, DelimiterProcessor> map) {
    for (DelimiterProcessor delimiterProcessor : delimiterProcessors) {
      char opening = delimiterProcessor.getOpeningCharacter();
      char closing = delimiterProcessor.getClosingCharacter();
      if (opening == closing) {
        DelimiterProcessor old = map.get(opening);
        if (old != null && old.getOpeningCharacter() == old.getClosingCharacter()) {
          StaggeredDelimiterProcessor s;
          if (old instanceof StaggeredDelimiterProcessor) {
            s = (StaggeredDelimiterProcessor) old;
          } else {
            s = new StaggeredDelimiterProcessor(opening);
            s.add(old);
          }
          s.add(delimiterProcessor);
          map.put(opening, s);
        } else {
          addDelimiterProcessorForChar(opening, delimiterProcessor, map);
        }
      } else {
        addDelimiterProcessorForChar(opening, delimiterProcessor, map);
        addDelimiterProcessorForChar(closing, delimiterProcessor, map);
      }
    }
  }

  private static void addDelimiterProcessorForChar(char delimiterChar, DelimiterProcessor toAdd, Map<Character, DelimiterProcessor> delimiterProcessors) {
    DelimiterProcessor existing = delimiterProcessors.put(delimiterChar, toAdd);
    if (existing != null) {
      throw new IllegalArgumentException("Delimiter processor conflict with delimiter char '" + delimiterChar + "'");
    }
  }

  /**
   * Parse content in block into inline children, using reference map to resolve references.
   */
  @Override
  public void parse(String content, Node block) {
    reset(content.trim());

    Node previous = null;
    while (true) {
      Node node = parseInline(previous);
      previous = node;
      if (node != null) {
        block.appendChild(node);
      } else {
        break;
      }
    }
    //node解析完成，用双向链表存储
    processDelimiters(null);
    mergeChildTextNodes(block);
  }

  void reset(String content) {
    this.input = content;
    this.index = 0;
    this.lastDelimiter = null;
    this.lastBracket = null;
  }


  private Text text(String text, int beginIndex, int endIndex) {
    return new Text(text.substring(beginIndex, endIndex));
  }

  private Text text(String text) {
    return new Text(text);
  }

  /**
   * Parse the next inline element in subject, advancing input index.
   * On success, return the new inline node.
   * On failure, return null.
   */
  private Node parseInline(Node previous) {
    char c = peek();
    if (c == '\0') {
      return null;
    }

    Node node;
    switch (c) {
      case '\n':
        node = parseNewline(previous);
        break;
      case '\\':
        node = parseBackslash();
        break;
//      case '`':
//        node = parseBackticks();
//        break;
      case '[':
        node = parseOpenBracket();
        break;
      case '!':
        node = parseBang();
        break;
      case ']':
        node = parseCloseBracket();
        break;
      case '<':
        node = parseAutolink(); //解析自动跳转标签：email，tel等
        if (node == null) { //解析菱形语法
          node = parseMd();
        }
        if (node == null) {
//          node = parseHtmlInline();   //解析Html标签
            node = parseString();
        }
        break;
      case '&':
        node = parseEntity();
        break;
      default:
        boolean isDelimiter = delimiterCharacters.get(c);
        if (isDelimiter) {
          DelimiterProcessor delimiterProcessor = delimiterProcessors.get(c);
          node = parseDelimiters(delimiterProcessor, c);
        } else {
          node = parseString();
        }
        break;
    }
    if (node != null) {
      return node;
    } else {
      index++;
      // When we get here, it's only for a single special character that turned out to not have a special meaning.
      // So we shouldn't have a single surrogate here, hence it should be ok to turn it into a String.
      String literal = String.valueOf(c);
      return text(literal);
    }
  }

  /**
   * If RE matches at current index in the input, advance index and return the match; otherwise return null.
   */
  private String match(Pattern re) {
    if (index >= input.length()) {
      return null;
    }
    Matcher matcher = re.matcher(input);
    matcher.region(index, input.length());
    boolean m = matcher.find();
    if (m) {
      index = matcher.end();
      return matcher.group();
    } else {
      return null;
    }
  }

  private Matcher matchAndReturn(Pattern re) {
    if (index >= input.length()) {
      return null;
    }
    Matcher matcher = re.matcher(input);
    matcher.region(index, input.length());
    boolean m = matcher.find();
    if (m) {
      return matcher;
    } else {
      return null;
    }
  }

  /**
   * Returns the char at the current input index, or {@code '\0'} in case there are no more characters.
   */
  private char peek() {
    if (index < input.length()) {
      return input.charAt(index);
    } else {
      return '\0';
    }
  }

  /**
   * Parse zero or more space characters, including at most one newline.
   */
  private void spnl() {
    match(SPNL);
  }

  /**
   * Parse a newline. If it was preceded by two spaces, return a hard line break; otherwise a soft line break.
   */
  private Node parseNewline(Node previous) {
    index++; // assume we're at a \n

    // Check previous text for trailing spaces.
    // The "endsWith" is an optimization to avoid an RE match in the common case.
    if (previous instanceof Text && ((Text) previous).getLiteral().endsWith(" ")) {
      Text text = (Text) previous;
      String literal = text.getLiteral();
      Matcher matcher = FINAL_SPACE.matcher(literal);
      int spaces = matcher.find() ? matcher.end() - matcher.start() : 0;
      if (spaces > 0) {
        text.setLiteral(literal.substring(0, literal.length() - spaces));
      }
      if (spaces >= 2) {
        return new HardLineBreak();
      } else {
        return new SoftLineBreak();
      }
    } else {
      return new SoftLineBreak(); //\n使用硬换行
    }
  }

  /**
   * Parse a backslash-escaped special character, adding either the escaped  character, a hard line break
   * (if the backslash is followed by a newline), or a literal backslash to the block's children.
   */
  private Node parseBackslash() {
    index++;
    Node node;
    if (peek() == '\n') {
      node = new HardLineBreak();
      index++;
    } else if (index < input.length() && ESCAPABLE.matcher(input.substring(index, index + 1)).matches()) {
      node = text(input, index, index + 1);
      index++;
    } else {
      node = text("\\");
    }
    return node;
  }

  /**
   * Attempt to parse backticks, returning either a backtick code span or a literal sequence of backticks.
   */
  private Node parseBackticks() {
    String ticks = match(TICKS_HERE);
    if (ticks == null) {
      return null;
    }
    int afterOpenTicks = index;
    String matched;
    while ((matched = match(TICKS)) != null) {
      if (matched.equals(ticks)) {
        Code node = new Code();
        String content = input.substring(afterOpenTicks, index - ticks.length());
        content = content.replace('\n', ' ');

        // spec: If the resulting string both begins and ends with a space character, but does not consist
        // entirely of space characters, a single space character is removed from the front and back.
        if (content.length() >= 3 &&
            content.charAt(0) == ' ' &&
            content.charAt(content.length() - 1) == ' ' &&
            Parsing.hasNonSpace(content)) {
          content = content.substring(1, content.length() - 1);
        }

        node.setLiteral(content);
        return node;
      }
    }
    // If we got here, we didn't match a closing backtick sequence.
    index = afterOpenTicks;
    return text(ticks);
  }

  /**
   * Attempt to parse delimiters like emphasis, strong emphasis or custom delimiters.
   */
  private Node parseDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) {
    DelimiterData res = scanDelimiters(delimiterProcessor, delimiterChar);
    if (res == null) {
      return null;
    }
    int length = res.count;
    int startIndex = index;

    index += length;
    Text node = text(input, startIndex, index);

    // Add entry to stack for this opener
    lastDelimiter = new Delimiter(node, delimiterChar, res.canOpen, res.canClose, lastDelimiter);
    lastDelimiter.length = length;
    lastDelimiter.originalLength = length;
    if (lastDelimiter.previous != null) {
      lastDelimiter.previous.next = lastDelimiter;
    }

    return node;
  }

  /**
   * Add open bracket to delimiter stack and add a text node to block's children.
   */
  private Node parseOpenBracket() {
    int startIndex = index;
    index++;

    Text node = text("[");

    // Add entry to stack for this opener
    addBracket(Bracket.link(node, startIndex, lastBracket, lastDelimiter));

    return node;
  }

  /**
   * If next character is [, and ! delimiter to delimiter stack and add a text node to block's children.
   * Otherwise just add a text node.
   */
  private Node parseBang() {
    int startIndex = index;
    index++;
    if (peek() == '[') {
      index++;

      Text node = text("![");

      // Add entry to stack for this opener
      addBracket(Bracket.image(node, startIndex + 1, lastBracket, lastDelimiter));

      return node;
    } else {
      return text("!");
    }
  }

  /**
   * Try to match close bracket against an opening in the delimiter stack. Return either a link or image, or a
   * plain [ character. If there is a matching delimiter, remove it from the delimiter stack.
   */
  private Node parseCloseBracket() {
    index++;
    int startIndex = index;

    // Get previous `[` or `![`
    Bracket opener = lastBracket;
    if (opener == null) {
      // No matching opener, just return a literal.
      return text("]");
    }

    if (!opener.allowed) {
      // Matching opener but it's not allowed, just return a literal.
      removeLastBracket();
      return text("]");
    }

    // Check to see if we have a link/image

    String dest = null;
    String title = null;
    boolean isLinkOrImage = false;

    // Maybe a inline link like `[foo](/uri "title")`
    if (peek() == '(') {
      index++;
      spnl();
      if ((dest = parseLinkDestination()) != null) {
        spnl();
        // title needs a whitespace before
        if (WHITESPACE.matcher(input.substring(index - 1, index)).matches()) {
          title = parseLinkTitle();
          spnl();
        }
        if (peek() == ')') {
          index++;
          isLinkOrImage = true;
        } else {
          index = startIndex;
        }
      }
    }

    // Maybe a reference link like `[foo][bar]`, `[foo][]` or `[foo]`
    if (!isLinkOrImage) {

      // See if there's a link label like `[bar]` or `[]`
      int beforeLabel = index;
      parseLinkLabel();
      int labelLength = index - beforeLabel;
      String ref = null;
      if (labelLength > 2) {
        ref = input.substring(beforeLabel, beforeLabel + labelLength);
      } else if (!opener.bracketAfter) {
        // If the second label is empty `[foo][]` or missing `[foo]`, then the first label is the reference.
        // But it can only be a reference when there's no (unescaped) bracket in it.
        // If there is, we don't even need to try to look up the reference. This is an optimization.
        ref = input.substring(opener.index, startIndex);
      }

      if (ref != null) {
        String label = Escaping.normalizeReference(ref);
        LinkReferenceDefinition definition = context.getLinkReferenceDefinition(label);
        if (definition != null) {
          dest = definition.getDestination();
          title = definition.getTitle();
          isLinkOrImage = true;
        }
      }
    }

    if (isLinkOrImage) {
      // If we got here, open is a potential opener
      Node linkOrImage = opener.image ? new Image(dest, title) : new Link(dest, title);

      Node node = opener.node.getNext();
      while (node != null) {
        Node next = node.getNext();
        linkOrImage.appendChild(node);
        node = next;
      }

      // Process delimiters such as emphasis inside link/image
      processDelimiters(opener.previousDelimiter);
      mergeChildTextNodes(linkOrImage);
      // We don't need the corresponding text node anymore, we turned it into a link/image node
      opener.node.unlink();
      removeLastBracket();

      // Links within links are not allowed. We found this link, so there can be no other link around it.
      if (!opener.image) {
        Bracket bracket = lastBracket;
        while (bracket != null) {
          if (!bracket.image) {
            // Disallow link opener. It will still get matched, but will not result in a link.
            bracket.allowed = false;
          }
          bracket = bracket.previous;
        }
      }

      return linkOrImage;

    } else { // no link or image
      index = startIndex;
      removeLastBracket();

      return text("]");
    }
  }

  private void addBracket(Bracket bracket) {
    if (lastBracket != null) {
      lastBracket.bracketAfter = true;
    }
    lastBracket = bracket;
  }

  private void removeLastBracket() {
    lastBracket = lastBracket.previous;
  }

  /**
   * Attempt to parse link destination, returning the string or null if no match.
   */
  private String parseLinkDestination() {
    int afterDest = LinkScanner.scanLinkDestination(input, index);
    if (afterDest == -1) {
      return null;
    }

    String dest;
    if (peek() == '<') {
      // chop off surrounding <..>:
      dest = input.substring(index + 1, afterDest - 1);
    } else {
      dest = input.substring(index, afterDest);
    }

    index = afterDest;
    return Escaping.unescapeString(dest);
  }

  /**
   * Attempt to parse link title (sans quotes), returning the string or null if no match.
   */
  private String parseLinkTitle() {
    int afterTitle = LinkScanner.scanLinkTitle(input, index);
    if (afterTitle == -1) {
      return null;
    }

    // chop off ', " or parens
    String title = input.substring(index + 1, afterTitle - 1);
    index = afterTitle;
    return Escaping.unescapeString(title);
  }

  /**
   * Attempt to parse a link label, returning number of characters parsed.
   */
  int parseLinkLabel() {
    if (index >= input.length() || input.charAt(index) != '[') {
      return 0;
    }

    int startContent = index + 1;
    int endContent = LinkScanner.scanLinkLabelContent(input, startContent);
    // spec: A link label can have at most 999 characters inside the square brackets.
    int contentLength = endContent - startContent;
    if (endContent == -1 || contentLength > 999) {
      return 0;
    }
    if (endContent >= input.length() || input.charAt(endContent) != ']') {
      return 0;
    }
    index = endContent + 1;
    return contentLength + 2;
  }

  /**
   * Attempt to parse an autolink (URL or email in pointy brackets).
   */
  private Node parseAutolink() {
    String m;
    if ((m = match(EMAIL_AUTOLINK)) != null) {
      String dest = m.substring(1, m.length() - 1);
      Link node = new Link("mailto:" + dest, null);
      node.appendChild(new Text(dest));
      return node;
    } else if ((m = match(AUTOLINK)) != null) {
      String dest = m.substring(1, m.length() - 1);
      Link node = new Link(dest, null);
      node.appendChild(new Text(dest));
      return node;
    } else {
      return null;
    }
  }

  /**
   * 尝试解析菱形语法
   */
  private Node parseMd() {
    String m;
    if ((m = match(MD_PATTERN)) != null) {
//            String dest = m.substring(1, m.length() - 1);
//            Link node = new Link("mailto:" + dest, null);
//            Text node = new Text("[" + "菱形" + "]");
//            Link node = new Link("https://www.baidu.com", "百度");
//            HtmlInline node = new HtmlInline();
//            node.setLiteral("AAA");
      MentionLinkNode node = new MentionLinkNode("https://www.baidu.com", "百度");
      node.setLiteral("AAA");
      return node;
    } else {
      return null;
    }
  }

  /**
   * Attempt to parse inline HTML.
   */
  private Node parseHtmlInline() {
    String m = match(HTML_TAG);
    if (m != null) {
      HtmlInline node = new HtmlInline();
      node.setLiteral(m);
      return node;
    } else {
      return null;
    }
  }

  /**
   * Attempt to parse a HTML style entity.
   */
  private Node parseEntity() {
    String m;
    if ((m = match(ENTITY_HERE)) != null) {
      return text(Html5Entities.entityToString(m));
    } else {
      return null;
    }
  }

  /**
   * Parse a run of ordinary characters, or a single character with a special meaning in markdown, as a plain string.
   */
  private Node parseString() {
    int begin = index;
    int length = input.length();
    while (index != length) {
      if (specialCharacters.get(input.charAt(index))) {
        break;
      }
      index++;
    }
    if (begin != index) {
      return text(input, begin, index);
    } else {
      return null;
    }
  }

  /**
   * Scan a sequence of characters with code delimiterChar, and return information about the number of delimiters
   * and whether they are positioned such that they can open and/or close emphasis or strong emphasis.
   *
   * @return information about delimiter run, or {@code null}
   */
  private DelimiterData scanDelimiters(DelimiterProcessor delimiterProcessor, char delimiterChar) {
    int startIndex = index;

    int delimiterCount = 0;
    while (peek() == delimiterChar) {
      delimiterCount++;
      index++;
    }

    if (delimiterCount < delimiterProcessor.getMinLength()) {
      index = startIndex;
      return null;
    }

    String before = startIndex == 0 ? "\n" :
        input.substring(startIndex - 1, startIndex);

    char charAfter = peek();
    String after = charAfter == '\0' ? "\n" :
        String.valueOf(charAfter);

    // We could be more lazy here, in most cases we don't need to do every match case.
    boolean beforeIsPunctuation = PUNCTUATION.matcher(before).matches();
    boolean beforeIsWhitespace = UNICODE_WHITESPACE_CHAR.matcher(before).matches();
    boolean afterIsPunctuation = PUNCTUATION.matcher(after).matches();
    boolean afterIsWhitespace = UNICODE_WHITESPACE_CHAR.matcher(after).matches();

    //左：前面不是空白 并且 （后面不是符号 或 前面是空白 或 前面是符号）
    //TODO 搞懂为什么后面是个符号，就不认为这个分隔符为左翼
    boolean leftFlanking = !afterIsWhitespace &&
        (!afterIsPunctuation || beforeIsWhitespace || beforeIsPunctuation);
    //右：前面不是空白 并且 （前面不是符号 或 后面是空白 或 后面是符号）
    boolean rightFlanking = !beforeIsWhitespace &&
        (!beforeIsPunctuation || afterIsWhitespace || afterIsPunctuation);
    boolean canOpen;
    boolean canClose;
    if (delimiterChar == '_') {
      canOpen = leftFlanking && (!rightFlanking || beforeIsPunctuation);
      canClose = rightFlanking && (!leftFlanking || afterIsPunctuation);
    } else {
      canOpen = leftFlanking && delimiterChar == delimiterProcessor.getOpeningCharacter();
      canClose = rightFlanking && delimiterChar == delimiterProcessor.getClosingCharacter();
    }

    //新算法，根据前面输入的分割符数量确定当前分隔符是开始分割符还是结束分割符
    //当前分隔符
    StringBuilder sb = new StringBuilder();
    int count = delimiterCount;
    while (count != 0) {
      sb.append(delimiterChar);
      count--;
    }
    String afterBlock = input.substring(0, index);
    String currentDelimiter = sb.toString();
    System.out.println("baizehahahahhaah: " + " after:" + afterBlock + " 分隔符：" + currentDelimiter);
    int matchCount = matchStringByIndexOf(afterBlock,currentDelimiter);
    System.out.println("baizehahahahhaah: " + " 匹配个数：" + matchCount);
    System.out.println("baizehahahahhaah: " + " 旧结果：" + "canOpen:" + canOpen + " " + " canClose:" + canClose);
    //算自己
    canOpen = matchCount % 2 == 1;  //奇数，认为是开始分隔符
    canClose = matchCount % 2 == 0;  //偶数，认为是结束分隔符

    index = startIndex;
    DelimiterData delimiterData = new DelimiterData(delimiterCount, canOpen, canClose);
    System.out.println("baizehahahahhaah" + " 修正后：");
    System.out.println("baizehahahahhaah" + " " + delimiterData.toString());
    return delimiterData;
  }

  //找出匹配个数
  private int matchStringByIndexOf(String parent, String target) {
    int count = 0;
    int index = 0;
    while ((index = parent.indexOf(target, index)) != -1) {
      index = index + target.length();
      count++;
    }
    System.out.println("匹配个数为" + count);                //结果输出
    return count;
  }

  private void processDelimiters(Delimiter stackBottom) {

    Map<Character, Delimiter> openersBottom = new HashMap<>();

    // find first closer above stackBottom:
    Delimiter closer = lastDelimiter;
    while (closer != null && closer.previous != stackBottom) {
      closer = closer.previous;
    }
    // move forward, looking for closers, and handling each
    while (closer != null) {
      char delimiterChar = closer.delimiterChar;

      DelimiterProcessor delimiterProcessor = delimiterProcessors.get(delimiterChar);
      if (!closer.canClose || delimiterProcessor == null) {
        closer = closer.next;
        continue;
      }

      char openingDelimiterChar = delimiterProcessor.getOpeningCharacter();

      // Found delimiter closer. Now look back for first matching opener.
      int useDelims = 0;
      boolean openerFound = false;
      boolean potentialOpenerFound = false;
      Delimiter opener = closer.previous;
      while (opener != null && opener != stackBottom && opener != openersBottom.get(delimiterChar)) {
        if (opener.canOpen && opener.delimiterChar == openingDelimiterChar) {
          potentialOpenerFound = true;
          useDelims = delimiterProcessor.getDelimiterUse(opener, closer);
          if (useDelims > 0) {
            openerFound = true;
            break;
          }
        }
        opener = opener.previous;
      }

      if (!openerFound) {
        if (!potentialOpenerFound) {
          // Set lower bound for future searches for openers.
          // Only do this when we didn't even have a potential
          // opener (one that matches the character and can open).
          // If an opener was rejected because of the number of
          // delimiters (e.g. because of the "multiple of 3" rule),
          // we want to consider it next time because the number
          // of delimiters can change as we continue processing.
          openersBottom.put(delimiterChar, closer.previous);
          if (!closer.canOpen) {
            // We can remove a closer that can't be an opener,
            // once we've seen there's no matching opener:
            removeDelimiterKeepNode(closer);
          }
        }
        closer = closer.next;
        continue;
      }

      Text openerNode = opener.node;
      Text closerNode = closer.node;

      // Remove number of used delimiters from stack and inline nodes.
      opener.length -= useDelims;
      closer.length -= useDelims;
      openerNode.setLiteral(
          openerNode.getLiteral().substring(0,
              openerNode.getLiteral().length() - useDelims));
      closerNode.setLiteral(
          closerNode.getLiteral().substring(0,
              closerNode.getLiteral().length() - useDelims));

      removeDelimitersBetween(opener, closer);
      // The delimiter processor can re-parent the nodes between opener and closer,
      // so make sure they're contiguous already. Exclusive because we want to keep opener/closer themselves.
      mergeTextNodesBetweenExclusive(openerNode, closerNode);
      delimiterProcessor.process(openerNode, closerNode, useDelims);

      // No delimiter characters left to process, so we can remove delimiter and the now empty node.
      if (opener.length == 0) {
        removeDelimiterAndNode(opener);
      }

      if (closer.length == 0) {
        Delimiter next = closer.next;
        removeDelimiterAndNode(closer);
        closer = next;
      }
    }

    // remove all delimiters
    while (lastDelimiter != null && lastDelimiter != stackBottom) {
      removeDelimiterKeepNode(lastDelimiter);
    }
  }

  private void removeDelimitersBetween(Delimiter opener, Delimiter closer) {
    Delimiter delimiter = closer.previous;
    while (delimiter != null && delimiter != opener) {
      Delimiter previousDelimiter = delimiter.previous;
      removeDelimiterKeepNode(delimiter);
      delimiter = previousDelimiter;
    }
  }

  /**
   * Remove the delimiter and the corresponding text node. For used delimiters, e.g. `*` in `*foo*`.
   */
  private void removeDelimiterAndNode(Delimiter delim) {
    Text node = delim.node;
    node.unlink();
    removeDelimiter(delim);
  }

  /**
   * Remove the delimiter but keep the corresponding node as text. For unused delimiters such as `_` in `foo_bar`.
   */
  private void removeDelimiterKeepNode(Delimiter delim) {
    removeDelimiter(delim);
  }

  private void removeDelimiter(Delimiter delim) {
    if (delim.previous != null) {
      delim.previous.next = delim.next;
    }
    if (delim.next == null) {
      // top of stack
      lastDelimiter = delim.previous;
    } else {
      delim.next.previous = delim.previous;
    }
  }

  private void mergeTextNodesBetweenExclusive(Node fromNode, Node toNode) {
    // No nodes between them
    if (fromNode == toNode || fromNode.getNext() == toNode) {
      return;
    }

    mergeTextNodesInclusive(fromNode.getNext(), toNode.getPrevious());
  }

  private void mergeChildTextNodes(Node node) {
    // No children or just one child node, no need for merging
    if (node.getFirstChild() == node.getLastChild()) {
      return;
    }

    mergeTextNodesInclusive(node.getFirstChild(), node.getLastChild());
  }

  private void mergeTextNodesInclusive(Node fromNode, Node toNode) {
    Text first = null;
    Text last = null;
    int length = 0;

    Node node = fromNode;
    while (node != null) {
      if (node instanceof Text) {
        Text text = (Text) node;
        if (first == null) {
          first = text;
        }
        length += text.getLiteral().length();
        last = text;
      } else {
        mergeIfNeeded(first, last, length);
        first = null;
        last = null;
        length = 0;
      }
      if (node == toNode) {
        break;
      }
      node = node.getNext();
    }

    mergeIfNeeded(first, last, length);
  }

  private void mergeIfNeeded(Text first, Text last, int textLength) {
    if (first != null && last != null && first != last) {
      StringBuilder sb = new StringBuilder(textLength);
      sb.append(first.getLiteral());
      Node node = first.getNext();
      Node stop = last.getNext();
      while (node != stop) {
        sb.append(((Text) node).getLiteral());
        Node unlink = node;
        node = node.getNext();
        unlink.unlink();
      }
      String literal = sb.toString();
      first.setLiteral(literal);
    }
  }

  private static class DelimiterData {

    final int count;
    final boolean canClose;
    final boolean canOpen;

    DelimiterData(int count, boolean canOpen, boolean canClose) {
      this.count = count;
      this.canOpen = canOpen;
      this.canClose = canClose;
    }

    @Override
    public String toString() {
      return "DelimiterData{" +
          "count=" + count +
          ", canClose=" + canClose +
          ", canOpen=" + canOpen +
          '}';
    }
  }
}
